/*
 * Purl (A JavaScript URL parser) v2.3.1
 * Developed and maintanined by Mark Perkins, mark@allmarkedup.com
 * Source repository: https://github.com/allmarkedup/jQuery-URL-Parser
 * Licensed under an MIT-style license. See https://github.com/allmarkedup/jQuery-URL-Parser/blob/master/LICENSE for details.
 */

;(function(factory) {
  if (typeof define === 'function' && define.amd) {
    define(factory)
  } else {
    window.purl = factory()
  }
})(function() {
  var tag2attr = {
      a: 'href',
      img: 'src',
      form: 'action',
      base: 'href',
      script: 'src',
      iframe: 'src',
      link: 'href'
    },
    key = [
      'source',
      'protocol',
      'authority',
      'userInfo',
      'user',
      'password',
      'host',
      'port',
      'relative',
      'path',
      'directory',
      'file',
      'query',
      'fragment'
    ], // keys available to query
    aliases = { anchor: 'fragment' }, // aliases for backwards compatability
    parser = {
      strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/, //less intuitive, more accurate to the specs
      loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/ // more intuitive, fails on relative paths and deviates from specs
    },
    isint = /^[0-9]+$/

  function parseUri(url, strictMode) {
    var str = decodeURI(url),
      res = parser[strictMode || false ? 'strict' : 'loose'].exec(str),
      uri = { attr: {}, param: {}, seg: {} },
      i = 14

    while (i--) {
      uri.attr[key[i]] = res[i] || ''
    }

    // build query and fragment parameters
    uri.param['query'] = parseString(uri.attr['query'])
    uri.param['fragment'] = parseString(uri.attr['fragment'])

    // split path and fragement into segments
    uri.seg['path'] = uri.attr.path.replace(/^\/+|\/+$/g, '').split('/')
    uri.seg['fragment'] = uri.attr.fragment.replace(/^\/+|\/+$/g, '').split('/')

    // compile a 'base' domain attribute
    uri.attr['base'] = uri.attr.host
      ? (uri.attr.protocol ? uri.attr.protocol + '://' + uri.attr.host : uri.attr.host) +
        (uri.attr.port ? ':' + uri.attr.port : '')
      : ''

    return uri
  }

  function getAttrName(elm) {
    var tn = elm.tagName
    if (typeof tn !== 'undefined') return tag2attr[tn.toLowerCase()]
    return tn
  }

  function promote(parent, key) {
    if (parent[key].length === 0) return (parent[key] = {})
    var t = {}
    for (var i in parent[key]) t[i] = parent[key][i]
    parent[key] = t
    return t
  }

  function parse(parts, parent, key, val) {
    var part = parts.shift()
    if (!part) {
      if (isArray(parent[key])) {
        parent[key].push(val)
      } else if ('object' == typeof parent[key]) {
        parent[key] = val
      } else if ('undefined' == typeof parent[key]) {
        parent[key] = val
      } else {
        parent[key] = [parent[key], val]
      }
    } else {
      var obj = (parent[key] = parent[key] || [])
      if (']' == part) {
        if (isArray(obj)) {
          if ('' !== val) obj.push(val)
        } else if ('object' == typeof obj) {
          obj[keys(obj).length] = val
        } else {
          obj = parent[key] = [parent[key], val]
        }
      } else if (~part.indexOf(']')) {
        part = part.substr(0, part.length - 1)
        if (!isint.test(part) && isArray(obj)) obj = promote(parent, key)
        parse(parts, obj, part, val)
        // key
      } else {
        if (!isint.test(part) && isArray(obj)) obj = promote(parent, key)
        parse(parts, obj, part, val)
      }
    }
  }

  function merge(parent, key, val) {
    if (~key.indexOf(']')) {
      var parts = key.split('[')
      parse(parts, parent, 'base', val)
    } else {
      if (!isint.test(key) && isArray(parent.base)) {
        var t = {}
        for (var k in parent.base) t[k] = parent.base[k]
        parent.base = t
      }
      if (key !== '') {
        set(parent.base, key, val)
      }
    }
    return parent
  }

  function parseString(str) {
    return reduce(
      String(str).split(/&|;/),
      function(ret, pair) {
        try {
          pair = decodeURIComponent(pair.replace(/\+/g, ' '))
        } catch (e) {
          // ignore
        }
        var eql = pair.indexOf('='),
          brace = lastBraceInKey(pair),
          key = pair.substr(0, brace || eql),
          val = pair.substr(brace || eql, pair.length)

        val = val.substr(val.indexOf('=') + 1, val.length)

        if (key === '') {
          key = pair
          val = ''
        }

        return merge(ret, key, val)
      },
      { base: {} }
    ).base
  }

  function set(obj, key, val) {
    var v = obj[key]
    if (typeof v === 'undefined') {
      obj[key] = val
    } else if (isArray(v)) {
      v.push(val)
    } else {
      obj[key] = [v, val]
    }
  }

  function lastBraceInKey(str) {
    var len = str.length,
      brace,
      c
    for (var i = 0; i < len; ++i) {
      c = str[i]
      if (']' == c) brace = false
      if ('[' == c) brace = true
      if ('=' == c && !brace) return i
    }
  }

  function reduce(obj, accumulator) {
    var i = 0,
      l = obj.length >> 0,
      curr = arguments[2]
    while (i < l) {
      if (i in obj) curr = accumulator.call(undefined, curr, obj[i], i, obj)
      ++i
    }
    return curr
  }

  function isArray(vArg) {
    return Object.prototype.toString.call(vArg) === '[object Array]'
  }

  function keys(obj) {
    var key_array = []
    for (var prop in obj) {
      if (obj.hasOwnProperty(prop)) key_array.push(prop)
    }
    return key_array
  }

  function purl(url, strictMode) {
    if (arguments.length === 1 && url === true) {
      strictMode = true
      url = undefined
    }
    strictMode = strictMode || false
    url = url || window.location.toString()

    return {
      data: parseUri(url, strictMode),

      // get various attributes from the URI
      attr: function(attr) {
        attr = aliases[attr] || attr
        return typeof attr !== 'undefined' ? this.data.attr[attr] : this.data.attr
      },

      // return query string parameters
      param: function(param) {
        return typeof param !== 'undefined' ? this.data.param.query[param] : this.data.param.query
      },

      // return fragment parameters
      fparam: function(param) {
        return typeof param !== 'undefined'
          ? this.data.param.fragment[param]
          : this.data.param.fragment
      },

      // return path segments
      segment: function(seg) {
        if (typeof seg === 'undefined') {
          return this.data.seg.path
        } else {
          seg = seg < 0 ? this.data.seg.path.length + seg : seg - 1 // negative segments count from the end
          return this.data.seg.path[seg]
        }
      },

      // return fragment segments
      fsegment: function(seg) {
        if (typeof seg === 'undefined') {
          return this.data.seg.fragment
        } else {
          seg = seg < 0 ? this.data.seg.fragment.length + seg : seg - 1 // negative segments count from the end
          return this.data.seg.fragment[seg]
        }
      }
    }
  }

  purl.jQuery = function($) {
    if ($ != null) {
      $.fn.url = function(strictMode) {
        var url = ''
        if (this.length) {
          url = $(this).attr(getAttrName(this[0])) || ''
        }
        return purl(url, strictMode)
      }

      $.url = purl
    }
  }

  purl.jQuery(window.jQuery)

  return purl
})
